/**
 * Copyright (c) 2021 OceanBase
 * OceanBase CE is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#ifndef OCEANBASE_SQL_OB_ROUTE_POLICY_H
#define OCEANBASE_SQL_OB_ROUTE_POLICY_H
#include "share/ob_define.h"
#include "lib/net/ob_addr.h"
#include "lib/container/ob_iarray.h"
#include "lib/list/ob_list.h"
#include "common/ob_zone_status.h"
#include "common/ob_zone_type.h"
#include "share/partition_table/ob_partition_location.h"
#include "share/ob_server_locality_cache.h"
#include "share/ob_server_status.h"
namespace oceanbase {
namespace storage {
class ObPartitionService;
}
namespace sql {
class ObPhyPartitionLocationInfo;
class ObPhyTableLocationInfo;
enum ObRoutePolicyType {
  INVALID_POLICY = 0,
  READONLY_ZONE_FIRST = 1,
  ONLY_READONLY_ZONE = 2,
  UNMERGE_ZONE_FIRST = 3,
  UNMERGE_FOLLOWER_FIRST = 4,
  POLICY_TYPE_MAX
};

struct ObRoutePolicyCtx {
  ObRoutePolicyCtx()
      : policy_type_(POLICY_TYPE_MAX),
        consistency_level_(common::INVALID_CONSISTENCY),
        is_proxy_priority_hit_support_(false)
  {}

  TO_STRING_KV(K(policy_type_), K(consistency_level_), K(is_proxy_priority_hit_support_));

  ObRoutePolicyType policy_type_;
  common::ObConsistencyLevel consistency_level_;
  bool is_proxy_priority_hit_support_;
};

class ObRoutePolicy {
public:
  enum PositionType {
    SAME_SERVER = 0,
    SAME_IDC = 1,
    SAME_REGION = 2,
    OTHER_REGION = 3,
    POSITION_TYPE_MAX,
  };
  enum MergeStatus {
    MERGING,
    NOMERGING,
    MERGE_STATUS_MAX,
  };
  class ReplicaAttribute {
  public:
    ReplicaAttribute()
        : pos_type_(POSITION_TYPE_MAX),
          merge_status_(MERGE_STATUS_MAX),
          zone_status_(share::ObZoneStatus::UNKNOWN),
          zone_type_(common::ZONE_TYPE_INVALID),
          start_service_time_(0),
          server_stop_time_(0),
          server_status_(share::ObServerStatus::OB_DISPLAY_MAX)
    {}
    ~ReplicaAttribute()
    {}
    bool operator==(const ReplicaAttribute& other_attr) const
    {
      bool bool_ret = false;
      if (merge_status_ == other_attr.merge_status_ && zone_type_ == other_attr.zone_type_) {
        if (pos_type_ == SAME_SERVER) {
          bool_ret = (other_attr.pos_type_ == SAME_SERVER || other_attr.pos_type_ == SAME_IDC);
        } else if (pos_type_ == SAME_IDC) {
          bool_ret = (other_attr.pos_type_ == SAME_SERVER || other_attr.pos_type_ == SAME_IDC);
        } else {
          bool_ret = (pos_type_ == other_attr.pos_type_);
        }
      }
      return bool_ret;
    }
    void reset()
    {
      pos_type_ = POSITION_TYPE_MAX;
      merge_status_ = MERGE_STATUS_MAX;
      zone_status_ = share::ObZoneStatus::UNKNOWN;
      zone_type_ = common::ZONE_TYPE_INVALID;
      start_service_time_ = 0;
      server_stop_time_ = 0;
      server_status_ = share::ObServerStatus::OB_DISPLAY_MAX;
    }
    TO_STRING_KV(K(pos_type_), K(merge_status_), K(zone_type_), K(zone_status_), K(start_service_time_),
        K(server_stop_time_), K(server_status_));
    PositionType pos_type_;
    MergeStatus merge_status_;
    share::ObZoneStatus::Status zone_status_;
    common::ObZoneType zone_type_;
    int64_t start_service_time_;
    int64_t server_stop_time_;
    share::ObServerStatus::DisplayStatus server_status_;
  };
  class CandidateReplica final : public share::ObReplicaLocation {
  public:
    CandidateReplica() : ObReplicaLocation(), attr_(), is_filter_(false), replica_idx_(common::OB_INVALID_INDEX)
    {}
    CandidateReplica(const share::ObReplicaLocation& replica_location)
        : ObReplicaLocation(replica_location), attr_(), is_filter_(false), replica_idx_(common::OB_INVALID_INDEX)
    {}
    bool is_usable() const
    {
      return is_filter_ == false;
    }
    void reset()
    {
      ObReplicaLocation::reset();
      attr_.reset();
      is_filter_ = false;
    }
    int assign(CandidateReplica& other)
    {
      int ret = common::OB_SUCCESS;
      server_ = other.server_;
      role_ = other.role_;
      sql_port_ = other.sql_port_;
      reserved_ = other.reserved_;
      replica_type_ = other.replica_type_;
      attr_ = other.attr_;
      is_filter_ = other.is_filter_;
      replica_idx_ = other.replica_idx_;
      return ret;
    }

  public:
    TO_STRING_KV(K(server_), K(role_), K(sql_port_), K(replica_type_), K(attr_), K(is_filter_), K(replica_idx_));
    ReplicaAttribute attr_;
    bool is_filter_;
    int64_t replica_idx_;  // invalid
  };

public:
  ObRoutePolicy(const common::ObAddr& addr, storage::ObPartitionService& part_service)
      : local_addr_(addr),
        local_locality_(),
        server_locality_array_(),
        partition_service_(part_service),
        has_refresh_locality_(false),
        has_readonly_zone_(false),
        is_inited_(false)
  {}
  ~ObRoutePolicy()
  {}
  int init();
  int calculate_replica_priority(common::ObIArray<CandidateReplica>& candi_replicas, ObRoutePolicyCtx& ctx);
  int init_candidate_replicas(common::ObIArray<CandidateReplica>& candi_replicas);
  int select_replica_with_priority(const ObRoutePolicyCtx& route_policy_ctx,
      const common::ObIArray<CandidateReplica>& replica_array, ObPhyPartitionLocationInfo& phy_part_loc_info);
  int select_intersect_replica(ObRoutePolicyCtx& route_policy_ctx,
      common::ObIArray<ObPhyTableLocationInfo*>& phy_tbl_loc_info_list,
      common::ObList<ObRoutePolicy::CandidateReplica, common::ObArenaAllocator>& intersect_server_list,
      bool& is_proxy_hit);

  TO_STRING_KV(K(local_addr_), K(local_locality_), K(server_locality_array_), K(has_refresh_locality_),
      K(has_readonly_zone_), K(is_inited_));

  inline bool is_follower_first_route_policy_type(const ObRoutePolicyCtx& ctx) const
  {
    return !has_readonly_zone_ && (UNMERGE_FOLLOWER_FIRST == ctx.policy_type_);
  }

  static int get_server_locality(const common::ObAddr& addr,
      const common::ObIArray<share::ObServerLocality>& server_locality_array, share::ObServerLocality& svr_locality);
  static bool is_same_idc(const share::ObServerLocality& locality1, const share::ObServerLocality& locality2);
  static bool is_same_region(const share::ObServerLocality& locality1, const share::ObServerLocality& locality2);

protected:
  int init_candidate_replica(
      const common::ObIArray<share::ObServerLocality>& server_locality_array, CandidateReplica& candi_replica);
  int calc_position_type(const share::ObServerLocality& candi_locality, CandidateReplica& candi_replica);
  int calc_intersect_repllica(const common::ObIArray<ObPhyTableLocationInfo*>& phy_tbl_loc_info_list,
      common::ObList<ObRoutePolicy::CandidateReplica, common::ObArenaAllocator>& intersect_server_list);
  int get_merge_status(const share::ObServerLocality& candi_locality, CandidateReplica& candi_replica);
  int get_zone_status(const share::ObServerLocality& candi_locality, CandidateReplica& candi_replica);

  int filter_replica(common::ObIArray<CandidateReplica>& candi_replicas, ObRoutePolicyCtx& ctx);
  int weak_sort_replicas(common::ObIArray<CandidateReplica>& candi_replicas, ObRoutePolicyCtx& ctx);
  int strong_sort_replicas(common::ObIArray<CandidateReplica>& candi_replicas, ObRoutePolicyCtx& ctx);
  inline ObRoutePolicyType get_calc_route_policy_type(const ObRoutePolicyCtx& ctx) const
  {

    ObRoutePolicyType type = INVALID_POLICY;
    if (has_readonly_zone_) {
      if (UNMERGE_FOLLOWER_FIRST == ctx.policy_type_) {
        type = READONLY_ZONE_FIRST;
      } else {
        type = ctx.policy_type_;
      }
    } else {
      type = READONLY_ZONE_FIRST;
    }
    return type;
  }

protected:
  common::ObAddr local_addr_;
  share::ObServerLocality local_locality_;
  common::ObSEArray<share::ObServerLocality, 32> server_locality_array_;
  storage::ObPartitionService& partition_service_;
  bool has_refresh_locality_;
  bool has_readonly_zone_;
  bool is_inited_;
};

}  // namespace sql
}  // namespace oceanbase
#endif
